Redis 总结
Redis
缓存穿透
- 方案一:查存DB 时,如果数据不存在,预热一个特殊空值到缓存中。这样,后续查询都会命中缓存,但要对特殊值,解析处理。
- 方案二:构造一个布隆过滤器 BloomFilter ,初始化全量数据,当接到请求时,在BloomFilter 中判断这个key是否存在,如果不存在,直接返回即可,无需再查询缓存和 DB
- 方案三:在数据库访问前进行校验,对不合法的请求直接 return
缓存雪崩
缓存雪崩指的是在某个时间点,缓存中的大部分或全部数据同时失效,导致大量的请求直接落到数据库上,从而引发数据库的压力过大,甚至崩溃。 这种情况通常发生在缓存中的数据在同一时间段内过期,或者由于某种原因导致缓存失效。
解决:缓存数据创建多个备份,当一个过期失效后,可以访问其他备份。
缓存并发问题 为什么要加分布式锁
程序员漫画小灰 优惠券的血案 ![[Pasted image 20240112161208.png]]
优惠券列表在Redis中以List的形式存储,查询时的逻辑很简单:
1.查询缓存,如果缓存存在,返回结果
2.缓存不存在,查询数据库
3.把查询数据库的结果循环放入缓存
然而,当某个时间点缓存不存在,请求量又很大的时候,会出现缓存并发的问题。也就是多个线程会重复去查询DB,又重复去更新缓存。
这其中重复查询DB是次要问题,而重复更新缓存则是主要问题。假如有两个线程同时进入上述的第三个阶段,各自进行rpush操作,那么最终会在优惠券列表的缓存中插入两组同样的数据。
注意:虽然Redis是单线程,但是注意是各自进行rpush。也就是顺序执行的 所以高并发下操作Redis里面的数据,需要加锁 MySQL自动就给你加了行锁,范围锁,所以没这个问题。
但是加锁又肯定影响性能。。
综合来看 无论是Redis MQ ES MYSQL 都其实是数据库 在高并发下操作数据集合,就是要加锁的。
Q 缓存换成数据库可以避免这个问题吗? 如果这里的缓存换成数据库,也会有这个问题,也会插入相同的数据
Q 单体架构是否可以避免这个问题? 无论是分布式架构 还是单体架构 只要你的应用服务做了横向扩展,有多个节点。就会出现这个问题,所以这个问题应该和分布式架构和单体架构无关
Q 缓存预热和这个问题类似? 这个问题和数据并发竞争预热是一个问题,都可以使用分布式锁来解决
Q 抽取出共性模式? 对于不存在则创建,否则返回已经存在的这种单例模式,如果你希望这个创建过程只运行一次,那么是一定要加锁的。 因为实际上这种模式就是一个单例模式,单例对象的创建时要加锁的。
if (!exist) {
init(); //do create or insert
} else {
return exist
}
如果你的初始化操作时幂等的,就不需要加锁,也就是操作一次或者操作N次结果是一样的,比如这里你不是append list 而是 remove old list and create new list 那么就没有问题 ,或者你使用set做过滤
缓存key过大?
当访问缓存时,如果key对应的value过大,读写、加载很容易超时,容易引发网络拥堵。另外缓存的字段较多时,每个字段的变更都会引发缓存数据的变更,频繁的读写,导致慢查询。如果大key过期被缓存淘汰失效,预热数据要花费较多的时间,也会导致慢查询。
所以我们在设计缓存的时候,要注意缓存的粒度,既不能过大,如果过大很容易导致网络拥堵;也不能过小,如果太小,查询频率会很高,每次请求都要查询多次。
解决方案:
- 方案一:设置一个阈值,当value的长度超过阈值时,对内容启动压缩,降低kv的大小
- 方案二:评估大key所占的比例,由于很多框架采用池化技术,如:Memcache,可以预先分配大对象空间。真正业务请求时,直接拿来即用。
- 方案三:颗粒划分,将大key拆分为多个小key,独立维护,成本会降低不少
- 方案四:大key要设置合理的过期时间,尽量不淘汰那些大key
数据并发竞争预热
互联网系统典型的特点就是流量大,一旦缓存中的数据过期、或因某些原因被删除等,导致缓存中的数据为空,大量的并发线程请求(查询同一个key)就会一起并发查询数据库
,数据库的压力陡然增加。
如果请求量非常大,全部压在数据库,可能把数据库压垮,进而导致整个系统的服务不可用。
解决方案:
- 方案一:引入一把
全局锁
,当缓存未命中时,先尝试获取全局锁,如果拿到锁,才有资格去查询DB
,并将数据预热到缓存中。虽然,client端发起的请求非常多,但是由于拿不到锁,只能处于等待状态,当缓存中的数据预热成功后,再从缓存中获取
为了便于理解,简单画了个流程图。这里面特别注意一个点,由于有一个并发时间差,所以会有一个二次check缓存是否有值的校验,防止缓存预热重复覆盖。
- 方案二:缓存数据创建多个备份,当一个过期失效后,可以访问其他备份。
如何实现一个分布式锁?
答案: 1、数据库表,性能比较差 2、使用Lua脚本 (包含 SETNX + EXPIRE 两条指令) 3、SET的扩展命令(SET key value EX [NX|XX]) 4、Redlock 框架 5、Zookeeper Curator框架提供了现成的分布式锁